package com.elasticcloudservice.predict;


import com.filetool.util.MyUtil;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class Predict_fm {

    public static final int FLAVOR_TYPES = 15;
    public static final int ITERATORS = 20000;  //梯度下降最大迭代次数
    public static final double MAX_COST = 10E-30; //最大允许的损失函数值
    public static final double LEARNING_RATE = 0.01; //学习率
    public static final int K = 3; //因子分解机参数

    public static String[] predictVm(String[] ecsContent, String[] inputContent) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

        //训练时间跨度
        String firstTrainDate = (ecsContent[0].split("\t"))[2];
        int count = 1;
        for (int i = 1; i < ecsContent.length; i++) {
            if (ecsContent[i].contains("\t") && ecsContent[i].split("\t").length == 3) {
                count++;
            }
        }
        String lastTrainDate = (ecsContent[count - 1].split("\t"))[2];
        Long trainStart = df.parse(firstTrainDate).getTime();
        Long trainEnd = df.parse(lastTrainDate).getTime();
        int trainDays = (int) Math.ceil((trainEnd - trainStart) * 1.0 / 86400000) + 1;
        //预测时间跨度
        int typeNum = Integer.parseInt(inputContent[2]);
        String firstPredictDate = inputContent[typeNum + 6];
        String lastPredictDate = inputContent[typeNum + 7];
        Long predictStart = df.parse(firstPredictDate).getTime();
        Long predictEnd = df.parse(lastPredictDate).getTime();
        int predictDays = (int) Math.ceil((predictEnd - predictStart) / 86400000);

        //获取输入文件中的虚拟机信息
        List<String> nameListFromInput = getNameListFromInput(inputContent);

        String[] predict = new String[nameListFromInput.size()]; //预测结果
        int[][] flavorNumByDay = MyUtil.getFlavorNumByDay(ecsContent); //各种虚拟机每天出现的数量

        //对数据做平滑处理
        smooth(flavorNumByDay);

        //箱形图的方法去噪
//        smoothByBox(flavorNumByDay);

        int windowNum = trainDays / predictDays; //滑窗数量
        //统计各虚拟机在每个窗口中的数量
        int[][] flavorNumInWindow = new int[windowNum][FLAVOR_TYPES];
        for (int i = 0; i < windowNum; i++) {
            for (int j = 0; j < FLAVOR_TYPES; j++) {
                int sum = 0;
                for (int k = (windowNum - i - 1) * predictDays + 1; k < flavorNumByDay.length - i * predictDays; k++) {
                    sum += flavorNumByDay[k][j];
                }
                flavorNumInWindow[windowNum - i - 1][j] = sum;
            }
        }
        flavorNumInWindow = MyUtil.getSumOfFlavor(flavorNumInWindow); //各种虚拟机出现次数的累加
//        MyUtil.printArray(flavorNumInWindow);
        //训练模型
        Map resultMap = trainModel(flavorNumInWindow);

        //预测结果
        int[] predictResult = predictResult(flavorNumInWindow, resultMap);

        int i = 0;
        for (String vmName : nameListFromInput) {
            predict[i++] = vmName + " " + predictResult[Integer.parseInt(vmName.substring(6)) - 1];
        }

        //根据预测结果，分配虚拟机到服务器
        return AllocateMine.allocate(inputContent, predict);
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 箱形图去噪
     * @Date: 20:56 2018/3/31
     */
    private static void smoothByBox(int[][] flavorNumByDay) {

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            //对每种flavor的历史数据进行升序排序
            int[] sortedArray = new int[flavorNumByDay.length];

            for (int i = 0; i < sortedArray.length; i++) {
                sortedArray[i] = flavorNumByDay[i][j];
            }
            Arrays.sort(sortedArray);

            List<Integer> noZeroList = new ArrayList<>();
            for (int i = 0; i < sortedArray.length; i++) {
                if (sortedArray[i] != 0) {
                    noZeroList.add(sortedArray[i]);
                }
            }
            if (noZeroList.size() < 4)
                continue;
            double Q1 = (noZeroList.size() + 1) * 1.0 / 4 - 1;  // 1/4分位点
            double Q2 = (noZeroList.size() + 1) * 2.0 / 4 - 1;  // 2/4分位点
            double Q3 = (noZeroList.size() + 1) * 3.0 / 4 - 1;  // 3/4分位点

            //获取每个分位点的值
            int intPart = (int) Q1;
            double decimalPart = Q1 - intPart;
            double q1Value = noZeroList.get(intPart) * (1 - decimalPart) + noZeroList.get(intPart + 1) * decimalPart;

            intPart = (int) Q2;
            decimalPart = Q2 - intPart;
            double q2Value = noZeroList.get(intPart) * (1 - decimalPart) + noZeroList.get(intPart + 1) * decimalPart;

            intPart = (int) Q3;
            decimalPart = Q3 - intPart;
            double q3Value = noZeroList.get(intPart) * (1 - decimalPart) + noZeroList.get(intPart + 1) * decimalPart;

            double IQR = q3Value - q1Value;
            double upper = q3Value + K * IQR;  //上内限
            double lower = q1Value - K * IQR;  //下内限
            for (int i = noZeroList.size() - 1; i >= 0; i--) {
                int value = noZeroList.get(i);
                if (value <= upper) {
                    upper = value;
                    break;
                }
            }
            for (int i = 0; i < noZeroList.size(); i++) {
                int value = noZeroList.get(i);
                if (value >= lower) {
                    lower = value;
                    break;
                }
            }

            //若值大于upper值，则将该值用upper值替代
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] >= upper)
                    flavorNumByDay[i][j] = (int) Math.ceil(upper);
            }
        }

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < flavorNumByDay.length; i++) {
                list.add(flavorNumByDay[i][j]);
            }
            int frequence = 0;
            int maxFrequenceValue = 0;
            for (Integer i : list) {
                if (i != 0) {
                    if (Collections.frequency(list, i) > maxFrequenceValue) {
                        frequence = Collections.frequency(list, i);
                        maxFrequenceValue = i;
                    }
                }
            }

            int noZeroCount = 0;
            int noZeroSum = 0;
            double average = 0.0;
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] != 0) {
                    noZeroCount++;
                    noZeroSum += flavorNumByDay[i][j];
                }
            }
            if (noZeroCount == 0)
                average = 0;
            else
                average = Math.ceil(noZeroSum * 1.0 / noZeroCount);
            System.out.println("flavor" + (j + 1) + "的非零平均值：" + average + " 非零数量：" + noZeroCount);
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] > average * 3.5) {
                    flavorNumByDay[i][j] = maxFrequenceValue;
                }
            }
        }
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 平滑处理，用倒数第二大的值代替噪点 88+
     * @Date: 17:16 2018/3/19
     */
    private static void smooth(int[][] flavorNumByDay) {

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            //对每种flavor的历史数据进行升序排序
            int[] sortedArray = new int[flavorNumByDay.length];
            for (int i = 0; i < sortedArray.length; i++) {
                sortedArray[i] = flavorNumByDay[i][j];
            }
            Arrays.sort(sortedArray);

            //若最大值大于1.7倍的次大值，则将最大值用次大值替代
            int maxVaule = sortedArray[sortedArray.length - 1];
            int secondMaxValue = sortedArray[sortedArray.length - 2];
            if (maxVaule >= 1.7 * secondMaxValue && secondMaxValue != 0) {
                for (int i = 0; i < flavorNumByDay.length; i++) {
                    if (flavorNumByDay[i][j] == maxVaule)
                        flavorNumByDay[i][j] = secondMaxValue;
                }
            }
        }

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < flavorNumByDay.length; i++) {
                list.add(flavorNumByDay[i][j]);
            }
            int frequence = 0;
            int maxFrequenceValue = 0;
            for (Integer i : list) {
                if (i != 0) {
                    if (Collections.frequency(list, i) > maxFrequenceValue) {
                        frequence = Collections.frequency(list, i);
                        maxFrequenceValue = i;
                    }
                }
            }

            int noZeroCount = 0;
            int noZeroSum = 0;
            double average = 0.0;
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] != 0) {
                    noZeroCount++;
                    noZeroSum += flavorNumByDay[i][j];
                }
            }
            if (noZeroCount == 0)
                average = 0;
            else
                average = Math.ceil(noZeroSum * 1.0 / noZeroCount);
//            System.out.println("flavor" + (j + 1) + "的非零平均值：" + average + " 非零数量：" + noZeroCount);
            for (int i = 0; i < flavorNumByDay.length; i++) {
                if (flavorNumByDay[i][j] > average * 3.7) {
                    flavorNumByDay[i][j] = maxFrequenceValue;
                }
            }
        }
    }


    /**
     * @Author: Chy
     * @Param:
     * @Description: 预测最终结果
     * @Date: 16:28 2018/3/17
     */
    private static int[] predictResult(int[][] flavorNumInWindow, Map resultMap) {

        //归一化
        double[][] scaling = featureScaling(flavorNumInWindow);

        int[] predictResult = new int[FLAVOR_TYPES];
        double[][] weights = (double[][]) resultMap.get("weights");
        List<double[][]> vectorList = (List) resultMap.get("vectorList");
        //预测结果
        for (int j = 0; j < FLAVOR_TYPES; j++) {
            int predictNum = 0;
            double temp = 0.0;
            double[] x = new double[scaling.length - 1];
            for (int i = 1; i < scaling.length; i++) {
                temp += scaling[i][j] * weights[j][i];
                x[i - 1] = scaling[i][j];
            }
            double vectorSum = getSumOfFetures(x, vectorList.get(j));
//            System.out.println(vectorSum);
            temp = weights[j][0] + temp + vectorSum;
            predictNum = (int) Math.floor(temp * (flavorNumInWindow[flavorNumInWindow.length - 1][j] - flavorNumInWindow[0][j] + flavorNumInWindow[0][j]));
            int minus = predictNum - flavorNumInWindow[flavorNumInWindow.length - 1][j];
            if (minus < 0)
                predictResult[j] = 0;
            else
                predictResult[j] = minus;
        }

        return predictResult;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 训练模型
     * @Date: 12:23 2018/3/16
     */
    private static Map<String, Object> trainModel(int[][] flavorNumInWindow) {

        //归一化
        double[][] scaling = featureScaling(flavorNumInWindow);

        double[][] weights = new double[FLAVOR_TYPES][flavorNumInWindow.length];

        List<double[][]> vectorList = new ArrayList<>();

        for (int j = 0; j < FLAVOR_TYPES; j++) {  //遍历15次，获取15种flavor的权重

            double predictValue = 0.0;
            double[] weightsForEveflavor = new double[scaling.length]; //每种flavor的权重
//            for (int m = 0; m < weightsForEveflavor.length; m++) {
//                    weightsForEveflavor[m] = Math.random()*0.1;
//            }
            double[][] vector = new double[flavorNumInWindow.length - 1][K]; //分解因子
            for (int m = 0; m < vector.length; m++) {
                for (int n = 0; n < vector[m].length; n++)
                    vector[m][n] = 0.0001 * m;
            }

            double[] flavorNum = new double[scaling.length - 1];
            for (int m = 0; m < scaling.length - 1; m++) {
                flavorNum[m] = scaling[m][j];
            }

            int count = 0;
            while (count++ < ITERATORS) {
//                vector = vectorScaling(vector);
//                MyUtil.printArray(vector);
//                System.out.println("-----------第" + count + "次迭代---------");
                //计算预测值
                predictValue = 0;
                double sum = getSumOfFetures(flavorNum, vector);  //计算所有互异特征分量的乘积和
                for (int i = 0; i < scaling.length - 1; i++) {
                    predictValue += scaling[i][j] * weightsForEveflavor[i + 1];
                }
                predictValue += (weightsForEveflavor[0] + sum);

                //损失函数
                double diff = predictValue - scaling[scaling.length - 1][j];
                double costFunction = 0.5 * diff * diff;

//                if (count == ITERATORS - 1) {
//                    StringBuffer sb = new StringBuffer("j=" + j + " count=" + count + " diff:" + diff + " predictValue: " + predictValue +
//                            " costFunction:" + costFunction);
//                    System.out.println(sb.toString());
//                }

                //若损失函数已足够小，结束迭代
                if (Math.abs(costFunction) < MAX_COST) {
//                    StringBuffer sb = new StringBuffer("j=" + j + " count=" + count + " diff:" + diff + " predictValue: " + predictValue +
//                            " costFunction:" + costFunction);
//                    System.out.println(sb.toString());
                    break;
                }

                double lamda = 0.00001;
                //梯度下降法更新权重
                for (int k = 0; k < weightsForEveflavor.length; k++) {
                    if (k == 0) {
                        weightsForEveflavor[k] = weightsForEveflavor[k] * (1 - lamda) - LEARNING_RATE * diff;
                    } else if (k == weightsForEveflavor.length - 4) {
                        weightsForEveflavor[k] = weightsForEveflavor[k] * (1 - lamda) - LEARNING_RATE * diff * scaling[k][j] / k / k;
                    } else if (k == weightsForEveflavor.length - 3) {
                        weightsForEveflavor[k] = weightsForEveflavor[k] * (1 - lamda) - LEARNING_RATE * diff * scaling[k][j] / k / k;
                    } else if (k == weightsForEveflavor.length - 2) {
                        weightsForEveflavor[k] = weightsForEveflavor[k] * (1 - lamda) - LEARNING_RATE * diff * scaling[k][j] / k;
                    } else {
                        weightsForEveflavor[k] = weightsForEveflavor[k] * (1 - lamda) - LEARNING_RATE * diff * scaling[k][j];
                    }
//                    System.out.println("权重： " + weightsForEveflavor[k]);
                }
                //梯度下降法更新v
                for (int p = 0; p < vector.length; p++) {
                    for (int q = 0; q < K; q++) {

                        double temp = .0;
                        for (int i = 0; i < vector.length; i++) {
                            temp += vector[i][q] * scaling[i][j];
                        }
                        temp -= vector[p][q] * scaling[p][j];
                        vector[p][q] = vector[p][q] * (1 - lamda) - LEARNING_RATE * diff * scaling[p][j] * temp;
                    }
                }
            }
            vectorList.add(vector);
            for (int m = 0; m < weightsForEveflavor.length; m++) {
                weights[j][m] = weightsForEveflavor[m];
            }

        }
        Map<String, Object> map = new HashMap<>();
        map.put("weights", weights);
        map.put("vectorList", vectorList);
        return map;
    }


    /**
     * @Author: Chy
     * @Param:
     * @Description: 计算所有互异特征分量的乘积和
     * @Date: 13:44 2018/3/25
     */
    private static double getSumOfFetures(double[] x, double[][] vector) {

        double sum = .0;
        int featureNum = x.length;  //特征数量
        for (int i = 0; i < featureNum - 1; i++) {
            for (int j = i + 1; j < featureNum; j++) {
                sum += dotProduct(i, j, vector) * x[i] * x[j];
            }
        }

        return sum;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 计算矩阵vector[i]与vector转置[j]的点积
     * @Date: 14:44 2018/3/25
     */
    private static double dotProduct(int i, int j, double[][] vector) {
        double dotProduct = 0.0;
        for (int m = 0; m < K; m++) {
            dotProduct += vector[i][m] + vector[j][m];
        }

        return dotProduct;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 计算一个数的指定次方
     * @Date: 20:41 2018/3/18
     */
    private static double multiplication(int count, int value) {
        double result = 1.0;
        for (int i = 0; i < count; i++) {
            result *= value;
        }
        return result;
    }

    /**
     * @Author: Chy
     * @Param:
     * @Description: 归一化
     * @Date: 19:35 2018/3/17
     */
    public static double[][] featureScaling(int[][] flavorNumInWindow) {
        double[][] scaling = new double[flavorNumInWindow.length][FLAVOR_TYPES];

        for (int j = 0; j < FLAVOR_TYPES; j++) {
            int min = flavorNumInWindow[0][j];
            int max = flavorNumInWindow[flavorNumInWindow.length - 1][j];

            for (int i = 0; i < flavorNumInWindow.length; i++) {
                if (min == max && min == 0) {
                    scaling[i][j] = 0;
                } else if (min == max && min != 0) {
                    scaling[i][j] = 1;
                } else {
                    scaling[i][j] = (flavorNumInWindow[i][j] - min) * 1.0 / (max - min);
                }
            }
        }

        return scaling;
    }


    /**
     * @Author: Chy
     * @Param:
     * @Description: 获取输入文件中虚拟机的名称
     * @Date: 13:55 2018/3/10
     */
    private static List<String> getNameListFromInput(String[] inputContent) {
        List<String> nameList = new ArrayList<>();
        for (int i = 3; i < 3 + Integer.parseInt(inputContent[2]); i++) {
            nameList.add(inputContent[i].split(" ")[0]);
        }
        return nameList;
    }
}
